package com.magenta.maxoptra.geocoding.nominatim;

import com.magenta.maxoptra.gis.*;
import com.magenta.maxoptra.gis.GeoException;
import com.magenta.maxoptra.gis.GeoProviderSettings;
import com.magenta.maxoptra.gis.GeographicCoordinates;
import com.magenta.maxoptra.gis.MapProviderType;
import com.magenta.maxoptra.gis.GeoAddress;
import com.magenta.maxoptra.gis.utils.DoForwardGeocodingProfiler;
import com.magenta.maxoptra.gis.utils.DoReverseGeocodingProfiler;
import com.magenta.mrs.basegeocoding.BaseGeocoding;
import com.magenta.mrs.util.GeoInformationClient;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParserException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;

import static com.magenta.maxoptra.gis.GisParameter.base_url;
import static com.magenta.maxoptra.gis.GisParameter.region;

public class GeocodingNominatimImpl extends BaseGeocoding<String> implements IGeocoding, IReverseGeocoding {

    protected java.net.Proxy proxy;

    public String getProviderName() {
        return MapProviderType.nominatim.name();
    }

    @Override
    protected boolean accuracyIsAccurateEnough(String accuracyThreshold, String accuracy) {
        return false;
    }

    @Override
    protected List<GeoAddress> doFindAddressByKeyword(GeoProviderSettings settings, String keyword, int resultAddressLimit, List<String> accuracies) throws GeoException {
        return findAddressByKeyword(settings, keyword, resultAddressLimit);
    }

    @Override
    public void setClient(GeoInformationClient client) {
        this.client = client;
        HttpHost httpProxy = (HttpHost) client.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY);
        if (httpProxy != null) {
            String proxyHost = httpProxy.getHostName();
            int proxyPort = httpProxy.getPort();
            final Credentials credentials = ((DefaultHttpClient) client).getCredentialsProvider().getCredentials(
                    new AuthScope(proxyHost, proxyPort));

            SocketAddress socketAddress = new InetSocketAddress(proxyHost, proxyPort);
            this.proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddress);

            Authenticator.setDefault(new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    PasswordAuthentication p = new PasswordAuthentication(credentials.getUserPrincipal().getName(),
                            credentials.getPassword().toCharArray());
                    return p;
                }
            });
        }
    }

    protected List<GeoAddress> doFindAddressByKeyword(GeoProviderSettings settings, String region, String keyword, int resultAddressLimit, List<String> accuracies)
            throws GeoException {
        GeoAddress geoAddress = new GeoAddress();
        geoAddress.setState(region);
        geoAddress.setZip(keyword);
        return doForwardGeocoding(settings, geoAddress, resultAddressLimit);
    }

    public List<GeoAddress> findAddressByKeyword(GeoProviderSettings settings, String keyword, int resultAddressLimit) throws GeoException {
        DoForwardGeocodingProfiler profiler = new DoForwardGeocodingProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), keyword);
        NominatimWS instance = NominatimWS.getInstance();
        try {
            List<GeoAddress> addresses = instance.getCoordinate(proxy, getURL(), formatString(keyword), getCountryCode(), resultAddressLimit);
            profiler.logDuration(addresses.size());
            return addresses;
        } catch (Exception e) {
            profiler.logError(e.getMessage());
            throw new GeoException(e);
        }
    }

    @Override
    public List<GeoAddress> doForwardGeocoding(GeoProviderSettings settings, GeoAddress address, int resultAddressLimit)
            throws GeoException {
        return findAddressByKeyword(settings, formatString(address.toString()), resultAddressLimit);
    }

    public GeoAddress findBestAddressByKeyword(GeoProviderSettings settings, String keyword) throws GeoException {
        DoForwardGeocodingProfiler profiler = new DoForwardGeocodingProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), keyword);
        NominatimWS instance = NominatimWS.getInstance();
        try {
            List<GeoAddress> addresses = instance.getCoordinate(proxy, getURL(), formatString(keyword), getCountryCode(), 1);
            GeoAddress geoAddress = addresses != null && addresses.size() > 0 ? addresses.get(0) : null;
            profiler.logDuration(geoAddress);
            return geoAddress;
        } catch (Exception e) {
            profiler.logError(e.getMessage());
            throw new GeoException(e);
        }
    }

    @Override
    public GeoAddress doReverseGeocoding(GeographicCoordinates geoCoordinates) throws GeoException {
        DoReverseGeocodingProfiler profiler = new DoReverseGeocodingProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), geoCoordinates);
        NominatimWS instance = NominatimWS.getInstance();
        try {
            GeoAddress address = instance.getAddress(proxy, getURL(), geoCoordinates.getLatitude(), geoCoordinates.getLongitude());
            profiler.logDuration(address);
            return address;
        } catch (Exception e) {
            profiler.logError(e.getMessage());
            throw new GeoException(e);
        }
    }

    @Override
    public List<GeoAddress> doReverseGeocodeFull(List<GeographicCoordinates> geoCoordinates) throws GeoException {
        List<GeoAddress> result = new ArrayList<GeoAddress>();
        DoReverseGeocodingProfiler profiler = new DoReverseGeocodingProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), geoCoordinates);
        try {
            NominatimWS instance = NominatimWS.getInstance();
            for (GeographicCoordinates coordinates : geoCoordinates) {
                result.add(instance.getAddress(proxy, getURL(), coordinates.getLatitude(), coordinates.getLongitude()));
            }
            profiler.logDuration(result.size());
        } catch (Exception e) {
            profiler.logError(e.getMessage());
            throw new GeoException(e);
        }
        return result;

    }

    private String getURL() {
        return getGisStringProperty(base_url);
    }

    private String getCountryCode() {
        return getGisStringProperty(region);
    }

}

